#include "Pch.h"
#include "FileRequest.h"
#include "Config.h"

FileRequest::FileRequest(wxWindow *parent, const wxString &url, const wxString &name, PdbDatabase database, const wxString &path, bool newWindow) :
m_parent(parent),
m_urlString(url),
m_name(name),
m_database(database),
m_happy(0),
m_is(0),
m_buffer(0),
m_bufferSize(0),
m_maxBufferSize(0),
m_path(wxEmptyString),
//m_url(url)
m_localFile(0),
m_busy(0),
m_error(wxEmptyString),
m_url(url),
m_newWindow(newWindow)
{
	wxFileName fn;
	fn.AssignDir(path);
	const wxString prefixList[] = {
		wxEmptyString,
		_T("pdb")
	};
	const wxString extList[] = {
		_T(".pdb"),
		_T(".ent")
	};
	const wxString compList[] = {
		wxEmptyString,
		_T(".gz"),
		_T(".Z")
	};
	for (int i=0; i<4; i++) {
		if (i==2) {
			if (m_database == DATABASE_PDB) fn.AppendDir(name.Mid(1, 2));
			else if (m_database == DATABASE_ASTRAL) fn.AppendDir(name.Mid(2, 2));
		}
		for (int j=0; j<sizeof(extList)/sizeof(extList[0]); j++) {
			for (int k=0; k<sizeof(compList)/sizeof(compList[0]); k++) {
				wxString tempFileName = wxEmptyString;
				tempFileName << prefixList[i&1] << name << extList[j] << compList[k];
				fn.SetFullName(tempFileName);
				if (fn.FileExists()) {
					m_path = fn.GetFullPath();
					//m_is = new wxFileInputStream(m_path);
					//if (m_is) {
					m_localFile = 1;
					m_happy = 1;
					return;
					//}
				}
			}
		}
	}
	// Note that if we get to this point, we've appended second and third characters
	// of file name to the path as a directory.  Some file systems don't like huge
	// directories.
	wxString tempFileName = wxEmptyString;
	tempFileName << prefixList[0] << name << extList[0] << compList[0];
	fn.SetFullName(tempFileName);
	m_path = fn.GetFullPath();

	if (!m_url.IsOk()) return;
	wxProtocol *protocol = &m_url.GetProtocol();
	// 60 seconds...  high, but seems necessary.  Run into frequent issues
	// when lower...
	protocol->SetTimeout(60);
	if (!m_url.GetScheme().CmpNoCase(_T("http"))) {
		((wxHTTP*)protocol)->SetHeader(_T("User-Agent"), APP_NAME _T("/") APP_VERSION);
	}
	if (!protocol->Initialize()) {
		return;
	}
	m_is = 0;
	m_happy = 1;
}

int FileRequest::ReadData() {
	if (m_busy) {
		return 1;
	}
	m_busy = 1;
	if (!m_is) {
		// Note:  Other wxWidgets events can occur and be passed to
		// calling window while this call is sitting around.
		// m_busy prevents bad things from happening when that is
		// the case.  Some older logic at the window level
		// also currently prevents it, but m_busy is a more general
		// fix.
		m_is = m_url.GetInputStream();
		if (!m_is) {
			m_happy = 0;
			m_busy = 0;
			return 0;
		}
	}
	while (m_is->CanRead()) {
		int READ_LEN = 4096;
		if (READ_LEN + m_bufferSize > m_maxBufferSize) {
			// ~100 MB cap
			if (m_bufferSize >= 100*1024*1024) {
				m_busy = 0;
				m_happy = 0;
				m_error = wxString::Format(_T("Problem downloading %s: File is too big"), m_name.c_str(), m_name.c_str());
				return 0;
			}
			m_maxBufferSize += READ_LEN + m_bufferSize + (m_bufferSize/2);
			m_buffer = (unsigned char*) realloc(m_buffer, m_maxBufferSize);
		}
		// CanRead() but LastRead() == 0 seems to be another way of saying Eof().
		int lastRead = (int) m_is->Read(m_buffer+m_bufferSize, READ_LEN).LastRead();
		if (lastRead == 0) {
			m_busy = 0;
			return 0;
		}
		m_bufferSize += lastRead;
	}
	m_busy = 0;
	if (m_is->Eof()) return 0;
	return 1;
}

int FileRequest::Update() {
	// Shouldn't happen.
	if (m_localFile) return 0;

	if (ReadData()) return 1;
	if (!m_happy || (!m_is->IsOk() && !m_is->Eof())) {
		m_happy = 0;
		if (m_error.IsEmpty())
			m_error = wxString::Format(_T("Unable to download %s (%s)"), m_name.c_str(), m_urlString.c_str());
	}
	else {
		m_happy = 0;
		wxFileName fn(m_path);
		// Todo:  Switch to 0777 to wxS_DIR_DEFAULT on update to 2.9.x.
		if (!fn.Mkdir(0777, wxPATH_MKDIR_FULL)) {
			m_error = wxString::Format(_T("Unable to create directory %s"), m_path.c_str());
		}
		else {
			if (m_bufferSize >= 2 && m_buffer[0] == 0x1F && m_buffer[1] == 0x8B) {
				m_path.Append(_T(".gz"));
			}
			wxFile out(m_path, wxFile::write);

			if (!out.IsOpened()) {
				m_error = wxString::Format(_T("Unable to open %s for writing"), m_path.c_str());
			}
			else if (out.Write(m_buffer, m_bufferSize) != m_bufferSize) {
				wxRemoveFile(m_path);
				m_error = wxString::Format(_T("Unable to write response to %s. Drive may be full."), m_path.c_str());
			}
			else {
				out.Close();
				m_happy = 1;
			}
		}
	}
	return 0;
}

FileRequest::~FileRequest() {
	if (m_is) delete m_is;
	if (m_buffer) free(m_buffer);
}

void UnescapePath(wxString &path, wxString &name, int appendIfUnused) {
	int letterCount = 0;
	for (int i = name.Len()-1; i >= 0; i--) {
		wxString temp = wxString::Format(_T("%%%is"), i+1);
		if (path.Replace(temp, wxString(&name[i], 1), true)) {
			letterCount++;
		}
	}
	size_t fullCode = path.Replace(_T("%s"), name, true);

	if (appendIfUnused) {
		if (!fullCode && letterCount != name.Len()) {
			path << name;
		}
	}
}

FileRequest *CreateFileRequest(wxString &name, wxWindow *parent, int noPopup, bool newWindow) {
	name.Trim(0);
	name.Trim(1);

	if (!name.Len()) {
		return 0;
	}

	// Handle id.pdb and id.ent, just in case.
	if (name.Len() > 4 && name[4] == '.') {
		name.Truncate(4);
	}
	// Same for Astral.
	if (name.Len() > 7 && name[7] == '.') {
		name.Truncate(7);
	}

	name.MakeLower();

	wxString url;
	wxString path;

	PdbDatabase db;
	if (name.Len() == 4) {
		// PDB
		url = config->strings.pdbUrl;
		path = config->strings.pdbPath;
		db = DATABASE_PDB;
	}
	else if (name.Len() == 7 && name[0] == 'd') {
		// ASTRAL
		url = config->strings.astralUrl;
		path = config->strings.astralPath;
		db = DATABASE_ASTRAL;
	}
	else {
		if (!noPopup) {
			wxMessageDialog msg(parent, _T("Unrecognized name format."), _T("Error"), wxOK|wxICON_ERROR|wxCENTRE);
			msg.ShowModal();
		}
		return 0;
	}

	UnescapePath(url, name, 1);

	// Escaped path doesn't work with config dialog.
	//UnescapePath(path, name, 0);

	return new FileRequest(parent, url, name, db, path, newWindow);
}

class LoadWebDialog : public wxDialog {
public:
	wxString name;
	bool newWindow;
	wxTextCtrl *textCtrl;
	wxCheckBox *cbNewWindow;
	wxButton *align;
	bool Create(wxWindow *parent, int forceNoNewWindow) {
		textCtrl = 0;
		if (!wxDialog::Create(parent, wxID_ANY, _T("Load remote PDB file"), wxDefaultPosition, wxDefaultSize, (wxDEFAULT_DIALOG_STYLE & ~wxBORDER_MASK) | wxRESIZE_BORDER)) return 0;

		wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
		vsizer->AddSpacer(6);
		vsizer->Add(new wxStaticText(this, wxID_ANY, _T("Enter 4-letter pdb code or 7 letter ASTRAL code of file to load")), 0, wxLEFT | wxRIGHT, 2);
		vsizer->AddSpacer(6);
		vsizer->Add(textCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER), 1, wxEXPAND);

		if (!forceNoNewWindow) {
			vsizer->AddSpacer(6);
			vsizer->Add(cbNewWindow = new wxCheckBox(this, wxID_ANY, _T("Open in new window")));
			if (config->webInNewWindow) {
				cbNewWindow->SetValue(wxCHK_CHECKED);
			}
		}
		else {
			cbNewWindow = 0;
		}
		vsizer->AddSpacer(2);

		{
			wxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
			hsizer->Add(align = new wxButton(this, wxID_OK, _T("Load")));
			hsizer->AddSpacer(10);
			hsizer->Add(new wxButton(this, wxID_CANCEL, _T("Cancel")));
			hsizer->AddSpacer(2);
			vsizer->Add(hsizer, 0, wxALIGN_RIGHT);
		}
		vsizer->AddSpacer(4);

		wxBoxSizer *topSizer = new wxBoxSizer(wxHORIZONTAL);
		topSizer->AddSpacer(4);
		topSizer->Add(vsizer);
		topSizer->AddSpacer(4);

		UpdateButtons();
		SetSizerAndFit(topSizer);
		SetAutoLayout(1);

		textCtrl->SetValue(config->strings.lastOpenWeb);
		textCtrl->SetFocus();

		return 1;
	}

	void OnTextChange(wxCommandEvent& evt) {
		if (textCtrl) {
			UpdateButtons();
		}
	}

	int CanTryRequest() {
		int res;
		name = textCtrl->GetValue();
		FileRequest *request = CreateFileRequest(name, GetParent(), 1);
		if (request && request->m_happy) {
			res = 1;
		}
		else {
			res = 0;
		}
		delete request;
		return res;
	}

	void UpdateButtons() {
		if (CanTryRequest()) {
			align->Enable(true);
		}
		else {
			align->Enable(false);
		}
	}

	void OnOk() {
		newWindow = 0;
		if (cbNewWindow) {
			config->webInNewWindow = newWindow = cbNewWindow->GetValue() != wxCHK_UNCHECKED;
		}
		config->strings.lastOpenWeb = name;
		EndModal(wxID_OK);
	}

	void OnEnter(wxCommandEvent &evt) {
		if (CanTryRequest()) {
			OnOk();
		}
	}

	void OnButton(wxCommandEvent& evt) {
		if (evt.GetId() == wxID_OK) {
			OnOk();
		}
		else {
			EndModal(evt.GetId());
		}
	}
	DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(LoadWebDialog, wxDialog)
	EVT_TEXT(wxID_ANY, LoadWebDialog::OnTextChange)
	EVT_BUTTON(wxID_ANY, LoadWebDialog::OnButton)
	EVT_TEXT_ENTER(wxID_ANY, LoadWebDialog::OnEnter)
END_EVENT_TABLE()


FileRequest *OpenWebDialog(wxWindow *parent, int *newWindow, int forceNoNewWindow) {
	LoadWebDialog dlg;
	if (dlg.Create(parent, forceNoNewWindow) && dlg.ShowModal() == wxID_OK) {
		return CreateFileRequest(dlg.name, parent, 0, dlg.newWindow);
	}
	else {
		return 0;
	}
}

PDBData *LoadPDBFromString(const wxString &path) {
	if (wxFileExists(path)) {
		wxFile file(path);
		if (file.IsOpened()) {
			int fd = file.fd();
			file.Detach();
			wxFile file2(path);
			FileReader * reader = readerCreate(fd);
			if (reader) {
				wxFileName name(path);
				PDBData *pdb = LoadPDBFileReader(reader, name.GetName().ToAscii(), 1, 5, 0, 1);
				if (pdb) return pdb;
			}
		}
	}
	return 0;
}
